package com.haima.sage.bigdata.api.controller;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.haima.sage.bigdata.api.common.Constants;
import com.haima.sage.bigdata.api.entity.AppSystem;
import com.haima.sage.bigdata.api.entity.ComputeResult;
import com.haima.sage.bigdata.api.entity.storage.Storage;
import com.haima.sage.bigdata.api.exception.ValidateException;
import com.haima.sage.bigdata.api.process.*;
import com.haima.sage.bigdata.api.process.impl.*;
import com.haima.sage.bigdata.api.service.AuditLogService;
import com.haima.sage.bigdata.api.service.ComputeResultService;
import com.haima.sage.bigdata.api.service.StorageService;
import com.haima.sage.bigdata.api.vo.BaseParam;
import com.haima.sage.bigdata.api.vo.BaseVO;
import com.haima.sage.bigdata.api.vo.param.*;
import org.apache.commons.lang.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.sql.SQLException;
import java.util.Optional;

@RestController
public class DslController extends BaseController {
    private static final Logger LOG = LogManager.getLogger(DslController.class);

    private final StorageService storageService;
    private final ComputeResultService computeResultService;
    @Value("${com.haima.bigdata.query.api.max.batch.size}")
    private Integer maxBatchSize = 10;
    @Value("${com.haima.bigdata.query.api.agg.batch.size}")
    private Integer aggBatchSize = 5;

    public DslController(AuditLogService auditLogService, StorageService storageService,
                         ComputeResultService computeResultService) {
        super(auditLogService);
        this.storageService = storageService;
        this.computeResultService = computeResultService;
    }

    private ResponseEntity<String> validate(BaseVO vo,
                                            Optional<ComputeResult> table, String uri,
                                            String body, String sign) {
        ResponseEntity<String> res = validateBase(vo.getAppkey(),
                vo.getAppUserId(), vo.getAppUserName(), vo.getLongTimestamp());
        if (res != null) {
            LOG.error(res);
            return res;
        }
        AppSystem bs;
        try {
            bs = isForbidden(vo.getAppkey(), table);
            if (bs == null) {
                LOG.error(HttpStatus.FORBIDDEN + "," + Constants.NO_POWER_ACCESS_TABLE_ERROR_EN);
                return error(HttpStatus.FORBIDDEN, Constants.NO_POWER_ACCESS_TABLE_ERROR_EN,
                        Constants.NO_POWER_ACCESS_TABLE_ERROR_CN);
            }
        } catch (Exception e) {
            LOG.error(HttpStatus.INTERNAL_SERVER_ERROR + "," + Constants.APP_SYSTEM_SECRET_DECRYPT_ERROR_EN);
            return error(HttpStatus.INTERNAL_SERVER_ERROR, Constants.APP_SYSTEM_SECRET_DECRYPT_ERROR_EN,
                    Constants.APP_SYSTEM_SECRET_DECRYPT_ERROR_CN);
        }
        if (!validSign(bs.getSecret(), uri, body, sign)) {
            return error(HttpStatus.BAD_REQUEST, Constants.SIGN_ERROR_EN,
                    Constants.SIGN_ERROR_CN);
        }
        return null;
    }


    private AppSystem isForbidden(String appkey, Optional<ComputeResult> optional)
            throws Exception {
        if (optional.isPresent()) {
            ComputeResult computeResult = optional.get();
            AppSystem bs = getAppSystem(appkey, computeResult.getSystems());
            if (bs != null) return bs;
        }
        return null;
    }

    protected void validateTable(BaseParam param) throws ValidateException {
        if (StringUtils.isEmpty(param.getTable())) {
            throw new ValidateException("table must not empty",
                    "table不能为空");
        }
        if (!Constants.tablePattern.matcher(param.getTable()).matches()) {
            throw new ValidateException("table must match regex:" + Constants.tablePattern.pattern(),
                    "table必须符合正则：" + Constants.tablePattern.pattern());
        }
    }


    @PostMapping("/api/row")
    public ResponseEntity<String> rowPost(HttpServletRequest req,
                                          @RequestParam("sign") String sign,
                                          @RequestBody String body) {
        LOG.info("api/row request has received");
        ObjectMapper mapper = new ObjectMapper();
        RowParam param;
        try {
            param = mapper.readValue(body, RowParam.class);
        } catch (IOException e) {
            LOG.error("row params error");
            return error(HttpStatus.BAD_REQUEST, Constants.INPUT_JSON_FORMAT_ERROR_EN,
                    e.getMessage());
        }
        ParamProcess process = new RowParamProcess(param);
        return post(req, sign, body, param, process);
    }

    @PostMapping("/api/list")
    public ResponseEntity<String> listPost(HttpServletRequest req,
                                           @RequestParam("sign") String sign,
                                           @RequestBody String body) {
        LOG.info("api/list request has received");
        ObjectMapper mapper = new ObjectMapper();
        ListParam param;
        try {
            param = mapper.readValue(body, ListParam.class);
        } catch (IOException e) {
            LOG.error("list params error");
            return error(HttpStatus.BAD_REQUEST, Constants.INPUT_JSON_FORMAT_ERROR_EN,
                    e.getMessage());
        }
        ParamProcess process = new ListParamProcess(param);
        return post(req, sign, body, param, process);
    }

    @PostMapping("/api/top")
    public ResponseEntity<String> topPost(HttpServletRequest req,
                                          @RequestParam("sign") String sign,
                                          @RequestBody String body) {
        ObjectMapper mapper = new ObjectMapper();
        TopParam param;
        LOG.info("api/top request has received");
        try {
            param = mapper.readValue(body, TopParam.class);
        } catch (IOException e) {
            LOG.error("top params error");
            return error(HttpStatus.BAD_REQUEST, Constants.INPUT_JSON_FORMAT_ERROR_EN,
                    e.getMessage());
        }
        ParamProcess process = new TopParamProcess(param);
        return post(req, sign, body, param, process);
    }

    @PostMapping("/api/agg")
    public ResponseEntity<String> aggPost(HttpServletRequest req,
                                          @RequestParam("sign") String sign,
                                          @RequestBody String body) {
        LOG.info("api/agg request has received");
        ObjectMapper mapper = new ObjectMapper();
        AggParam param;
        try {
            param = mapper.readValue(body, AggParam.class);
        } catch (IOException e) {
            LOG.error("agg params error");
            return error(HttpStatus.BAD_REQUEST, Constants.INPUT_JSON_FORMAT_ERROR_EN,
                    e.getMessage());
        }
        if (param.getLimit() == null) param.setLimit(aggBatchSize);
        ParamProcess process = new AggParamProcess(param);
        return post(req, sign, body, param, process);
    }

    // @PostMapping("/api/column")
    public ResponseEntity<String> columnPost(HttpServletRequest req,
                                             @RequestParam("sign") String sign,
                                             @RequestBody String body) {
        ObjectMapper mapper = new ObjectMapper();
        ColumnParam param;
        try {
            param = mapper.readValue(body, ColumnParam.class);
        } catch (IOException e) {
            // return error(HttpStatus.BAD_REQUEST, Constants.INPUT_JSON_FORMAT_ERROR_EN,
              //      Constants.INPUT_JSON_FORMAT_ERROR_CN);
            return error(HttpStatus.BAD_REQUEST, Constants.INPUT_JSON_FORMAT_ERROR_EN,
                    e.getMessage());
        }
        ParamProcess process = new ColumnParamProcess(param);
        return post(req, sign, body, param, process);
    }

    private ResponseEntity<String> post(HttpServletRequest req, String sign,
                                        String body, BaseParam param,
                                        ParamProcess process) {
        try {
            validateTable(param);
            Optional<ComputeResult> opt = this.computeResultService.findById(param.getTable());
            String uri = req.getRequestURI();
            ResponseEntity<String> res = validate(param, opt, uri, body, sign);
            if (res != null) {
                LOG.error(res);
                return res;
            }
            Storage storage = opt.get().getStorage();
            LOG.info("api verification success");
            Object obj = process.execute(storage, opt.get().getTableName(), opt.get().getTablePattern(), maxBatchSize);
            return ok(obj);
        } catch (ValidateException e) {
            LOG.error(e.getMessage(), new Exception(e));
            return error(HttpStatus.BAD_REQUEST, e.getMsg(), e.getSubMsg());
        } catch (SQLException e){
            LOG.error(Constants.SQL_QUERY_ERROR_EN, new Exception(e));
            return error(HttpStatus.BAD_REQUEST, Constants.SQL_QUERY_ERROR_EN, e.getMessage());
        } catch (Exception e) {
            LOG.error(Constants.API_QUERY_ERROR_EN, new Exception(e));
            return error(HttpStatus.BAD_REQUEST, Constants.API_QUERY_ERROR_EN,
                    e.getMessage());
        }
    }
}
